/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package chatclient;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import shared.HeaderConstants;
import shared.MessageHandler;

/**
 *
 * @author freeman
 */
public class ClientThread extends Thread {

    private ClientGUI gui = null;
    private String clientUsernameStr = null;
    private String serverAddressStr = null;
    private String portNo = null;
    private boolean isAlive = true;
    private MessageHandler messageHandler = null;

    public ClientThread( ClientGUI gui, String clientUsernameStr,
            String serverAddressStr, String portNo ) {
        this.gui = gui;
        this.serverAddressStr = serverAddressStr;
        this.clientUsernameStr = clientUsernameStr;
        this.portNo = portNo;
        this.messageHandler = MessageHandler.getInstance(clientUsernameStr);
    }

    @Override
    public void run() {
        if ( connectAndInit() ) {
            gui.notifyConnectState( true );
            receiveAndProcessLoop();
        } else {
            gui.notifyConnectState( false );
        }
    }

    /*
     * Handles the process of connecting to the server and getting input 
     * streams.
     */
    private boolean connectAndInit() {
        if ( serverAddressStr.length() == 0 ) {
            gui.putMessage( "Invalid address." );
            return false;
        }
        if ( clientUsernameStr.length() == 0 ) {
            gui.putMessage( "Invalid username." );
            return false;
        }

        Integer portNoInt = null;
        try {
            portNoInt = new Integer( portNo );
        } catch ( Exception e ) {
            gui.putMessage( "Invalid port number." );
            return false;
        }

        try {
            gui.putMessage( "Connecting to [" + serverAddressStr
                    + ":" + portNo + "] as [" + clientUsernameStr + "] ..." );

            if ( !messageHandler.setSocket( new Socket( serverAddressStr, portNoInt ) ) ) {
                gui.putMessage( "Could not establish a connection" );
                return false;
            }

            if ( initServer() ) {
                return true;
            } else {
                /*real useful error message*/
                gui.putMessage( "Unable to connect." );
                return false;
            }

        } catch ( UnknownHostException ex ) {
            gui.putMessage( "Unknown host " + serverAddressStr + "..." );
            return false;
        } catch ( IOException ex ) {
            gui.putMessage( "Unable to connect..." );
            return false;
        }
    }

    /*
     * The init process just sends the username...
     */
    private boolean initServer() {
        /*create the username protocol message*/
        byte[] protoMessage = ( "username:" + this.clientUsernameStr ).getBytes();

        /*send the data to the server*/
        if ( !sendData( HeaderConstants.HEADER_INIT, protoMessage ) ) {
            System.err.println("ClientThread: ("+this.clientUsernameStr+") unable to init...");
            return false;
        }

        /*read the response from the server*/
        if ( !messageHandler.read() ) {
            System.err.println( "ClientThread: ("+this.clientUsernameStr+
                    ") Unable to read init response from server." );
            return false;
        }

        /*check to make sure the server sent an accept message*/
        if ( !messageHandler.headerEquals( HeaderConstants.HEADER_SVRACCEPT ) ) {
            System.err.println( "ClientThread: ("+this.clientUsernameStr+") Server did not send accept." );
            return false;
        } else {
            String messageForGui = messageHandler.getMessageString();
            this.gui.putMessage( messageForGui );
        }
        return true;
    }

    /*
     * Reads data from the client in a loop:
     * 1st read in the length of the message
     * 2nd read in the header byte
     * 3rd read in the message
     * 
     * Parsees the header to determine what to do.
     */
    private void receiveAndProcessLoop() {
        while ( isRunning() ) {
            /*attempt to read the next message*/
            if (! messageHandler.read() ) {
                System.err.println("ClientThread: ("+this.clientUsernameStr+") Unable to read, exiting");
                break;
            }
            
            System.err.println("ClientThread read header: " + messageHandler.headerString() );

            /*get info from handler*/
            byte[] message = messageHandler.getMessageBytes();

            /*determine what to do based on header from the server*/
            if ( messageHandler.headerEquals( HeaderConstants.HEADER_CHATMSG_PVT ) ) {
                System.out.println( "ClientThread: ("+this.clientUsernameStr+") Operation HEADER_CHATMSG_PVT not "
                        + "supported." );
            } else if ( messageHandler.headerEquals( HeaderConstants.HEADER_KICKED ) ) {
                gui.putMessage( "Kicked from server." );
                this.terminate();
            } else if ( messageHandler.headerEquals( HeaderConstants.HEADER_CHATMSG_PUB ) ) {
                gui.putMessage( new String( message ) );
            } else if ( messageHandler.headerEquals( HeaderConstants.HEADER_USERJOINED ) ) {
                System.out.println( "ClientThread: ("+this.clientUsernameStr+") Operation HEADER_USERJOINED not "
                        + "supported." );
            } else if ( messageHandler.headerEquals( HeaderConstants.HEADER_USERLEFT ) ) {
                System.out.println( "ClientThread: ("+this.clientUsernameStr+") Operation HEADER_USERLEFT not "
                        + "supported." );
            } else if ( messageHandler.headerEquals( HeaderConstants.HEADER_SVRUSRLIST ) ) {
                gui.updateUserList( new String( message ) );
            } else if ( messageHandler.headerEquals( HeaderConstants.HEADER_SERVERMSG ) ) {
                gui.putMessage( new String( message ) );
            } else if ( messageHandler.headerEquals( HeaderConstants.HEADER_CHANGEUSERNAME ) ) {
                clientUsernameStr = new String( message );
                gui.putMessage( "Your username was changed to "
                        + new String( message ) );
            } else {
                System.err.println( "ClientThread: ("+this.clientUsernameStr+") Server did not send a proper header: "
                        + messageHandler.headerString() );
            }
        }

        System.err.println( "ClientThread ("+this.clientUsernameStr+") exiting..." );
    }

    /*
     * Checks whether or not the clientthread is running.
     * 
     * @return true if the serverthread is running, false otherwise.
     */
    private synchronized boolean isRunning() {
        return isAlive;
    }

    /*
     * terminates the clientthread
     */
    private synchronized void terminate() {
        System.err.println( "ClientThread: ("+this.clientUsernameStr+") terminated." );
        gui.notifyConnectState( false );
        messageHandler.terminate();
        this.isAlive = false;
    }

    /*
     * Called by the GUI to send a public chat message to everyone
     * 
     * @param message: The message to send to all clients.
     */
    public void sendPubChatMessage( String message ) {
        sendData( HeaderConstants.HEADER_CHATMSG_PUB, message.getBytes() );
    }

    /*
     * Returns true if the client is connected to the server.
     * 
     * @return true if connected, false otherwise.
     */
    public boolean isConnected() {
        return messageHandler.isConnected();
    }

    /*
     * Send the disconnect message to the server, then notify
     * the gui that the state has changed from Connected to Disconnected
     */
    public void disconnect() {
        sendData( HeaderConstants.HEADER_DISCONNECT, "#".getBytes() );
        this.terminate();
    }

    /*
     * Sends data to the server
     * @param header: The header byte to send
     * @param cmdMsg: the message to send
     * 
     * @return: true if data sending was successful, false otherwise.
     */
    private boolean sendData( byte header[], byte[] cmdMsg ) {
        if ( !messageHandler.write( header, cmdMsg ) ) {
            System.err.println( "ClientThread: ("+this.clientUsernameStr+
                    ") cannot send data, message handler cannot write" );
            return false;
        }
        return true;
    }
}
